/* 
 * Copyright 2012 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.appspot.dispotrains.station;

import com.google.common.collect.Lists;

import android.content.Context;
import android.support.v4.content.AsyncTaskLoader;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.Node;
import org.jsoup.nodes.TextNode;
import org.jsoup.select.Elements;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Calendar;
import java.util.Date;
import java.util.Formatter;
import java.util.GregorianCalendar;
import java.util.List;

public class StationBuilder extends AsyncTaskLoader<Station> {
  private static final String INFOMOBI_TEMPLATE =
      "http://www.transport-idf.com/infomobi/rechercherDispoAsc.do?codeGare=%d&dateRecherche=%02d%%2F%02d%%2F%4d&serverAction=VALIDEZ";
  private static final String DISPOTRAINS_TEMPLATE =
      "http://sterops.pythonanywhere.com/json/GetGare/%d";

  private final int stationId;
  private Station station = null;

  /**
   * Create a new station builder.
   *
   * @param stationId ID of the station in the DispoTrains system
   */
  public StationBuilder(Context context, int stationId) {
    super(context);
    this.stationId = stationId;
  }

  /**
   * Given a URL, establishes an HttpUrlConnection and retrieves the web page
   * content as a InputStream, which it returns as a string.
   */
  private String downloadUrl(URL url) throws IOException {
    InputStream is = null;

    try {
      HttpURLConnection conn = (HttpURLConnection) url.openConnection();
      conn.setReadTimeout(10000 /* milliseconds */);
      conn.setConnectTimeout(15000 /* milliseconds */);
      conn.setRequestMethod("GET");
      conn.setDoInput(true);
      // Starts the query
      conn.connect();
      is = conn.getInputStream();

      // Convert the InputStream into a string
      String contentAsString = readIt(is);
      return contentAsString;

      // Makes sure that the InputStream is closed after the app is
      // finished using it.
    } finally {
      if (is != null) {
        is.close();
      }
    }
  }

  /**
   * @param doc document describing elevators and their status
   * @return a list of elevators contained in the document
   */
  private List<Elevator> extractElevatorsFromDocument(Document doc) {
    Element table = doc.getElementById("reponse_alerte");
    Node day = table.getElementById("date_consultation").childNode(1);
    Date date = parseDate(((TextNode) day).text().trim());

    List<Elevator> elevators = Lists.newArrayList();
    for (Element row : table.getElementsByTag("tbody").get(0).getElementsByTag("tr")) {
      Elements cols = row.getElementsByTag("td");
      if (cols.get(0).text().isEmpty()) {
        continue;
      }
      String code = cols.get(0).text().trim();
      String position = cols.get(1).text().trim();
      String direction = cols.get(2).text().trim();
      String state = cols.get(3).text().trim();
      elevators.add(new Elevator(code, position, direction, state, date));
    }
    return elevators;
  }


  /**
   * Return the URL for the given Infomobi station.
   *
   * @param id of the station
   * @return URL to download
   */
  private String getInfomobiUrl(int id) {
    Date today = new Date();
    Calendar calendar = new GregorianCalendar();
    calendar.setTime(today);
    
    StringBuilder sb = new StringBuilder();
    Formatter formatter = new Formatter(sb);
    formatter.format(INFOMOBI_TEMPLATE, id, calendar.get(Calendar.DAY_OF_MONTH),
        calendar.get(Calendar.MONTH) + 1, calendar.get(Calendar.YEAR));
    return sb.toString();
  }

  /**
   * Return the URL for JSON station info.
   *
   * @param id of the station
   * @return URL to download
   */
  private String getJsonUrl(int id) {
    StringBuilder sb = new StringBuilder();
    Formatter formatter = new Formatter(sb);
    formatter.format(DISPOTRAINS_TEMPLATE, id);
    return sb.toString();
  }

  private Station getStationInfo(int stationId)
      throws MalformedURLException, JSONException, IOException {
    JSONObject jsonStation = new JSONObject(downloadUrl(new URL(getJsonUrl(stationId))));
    String name = jsonStation.getString("nom");
    Integer id = jsonStation.getInt("id");
    Station station = new Station(id, name);

    JSONArray infomobi = jsonStation.getJSONArray("infomobi");
    for (int j = 0; j < infomobi.length(); j++) {
      station.addInfomobiCode(infomobi.getInt(j));
    }
    return station;
  }

  /**
   * @see android.support.v4.content.AsyncTaskLoader#loadInBackground()
   */
  @Override
  public Station loadInBackground() {
    Station station;
    try {
      station = getStationInfo(this.stationId);
    } catch (MalformedURLException e1) {
      return null;
    } catch (JSONException e1) {
      return null;
    } catch (IOException e1) {
      return null;
    }
    for (int id : station.getInfomobiCodes()) {
      try {
        Document doc = Jsoup.connect(getInfomobiUrl(id)).userAgent(
            "Mozilla/5.0 (iPhone; U; CPU iPhone OS 5_1_1 like Mac OS X; en) AppleWebKit/534.46.0 (KHTML, like Gecko) CriOS/19.0.1084.60 Mobile/9B206 Safari/7534.48.3").timeout(3000).get();
        List<Elevator> elevators = extractElevatorsFromDocument(doc);
        for (Elevator elevator : elevators) {
          station.addElevator(elevator);
        }
      } catch (IOException e) {
        return null;
      }
    }
    this.station = station;
    return station;
  }

  /**
   * Parses a date.
   *
   * @param day in DD/MM/YYYY format
   * @return the day represented by the string
   */
  private Date parseDate(String day) {
    String[] dateComponents = day.split("/");
    return new GregorianCalendar(Integer.parseInt(dateComponents[2]),
        Integer.parseInt(dateComponents[1]) - 1, Integer.parseInt(dateComponents[0])).getTime();
  }

  /**
   * Reads an InputStream and converts it to a String.
   * 
   * @param is
   * @return
   * @throws IOException
   * @throws UnsupportedEncodingException
   */
  private String readIt(InputStream is) throws IOException, UnsupportedEncodingException {
    BufferedReader br = new BufferedReader(new InputStreamReader(is));

    StringBuilder sb = new StringBuilder();

    String line;
    while ((line = br.readLine()) != null) {
      sb.append(line);
    }
    return sb.toString();
  }

  /**
   * Handles a request to start the Loader.
   */
  @Override
  protected void onStartLoading() {
    if (station != null && !takeContentChanged()) {
      // If we currently have a result available, deliver it
      // immediately.
      deliverResult(station);
    } else {
      forceLoad();
    }
  }


  /**
   * Handles a request to stop the Loader.
   */
  @Override
  protected void onStopLoading() {
    // Attempt to cancel the current load task if possible.
    cancelLoad();
  }

  /**
   * Handles a request to cancel a load.
   */
  @Override
  public void onCanceled(Station station) {
    super.onCanceled(station);
  }

  @Override
  protected void onReset() {
    super.onReset();

    // Ensure the loader is stopped
    onStopLoading();
  }
}